package cn.csg.iotplatform.module.system.service.auth;

import cn.csg.iotplatform.framework.common.enums.CommonStatusEnum;
import cn.csg.iotplatform.framework.common.enums.CommonStatusEnumx;
import cn.csg.iotplatform.framework.common.enums.UserTypeEnum;
import cn.csg.iotplatform.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.csg.iotplatform.framework.common.util.crypto.RSAUtil;
import cn.csg.iotplatform.framework.common.util.date.DateUtils;
import cn.csg.iotplatform.framework.common.util.monitor.TracerUtils;
import cn.csg.iotplatform.framework.common.util.servlet.ServletUtils;
import cn.csg.iotplatform.framework.common.util.validation.ValidationUtils;
import cn.csg.iotplatform.framework.web.core.util.WebFrameworkUtils;
import cn.csg.iotplatform.module.system.api.logger.dto.LoginLogCreateReqDTO;
import cn.csg.iotplatform.module.system.controller.admin.auth.vo.*;
import cn.csg.iotplatform.module.system.convert.auth.AuthConvert;
import cn.csg.iotplatform.module.system.dal.dataobject.dept.DeptDO;
import cn.csg.iotplatform.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.csg.iotplatform.module.system.dal.dataobject.security.SecurityConfigDO;
import cn.csg.iotplatform.module.system.dal.dataobject.user.AdminUserDO;
import cn.csg.iotplatform.module.system.dal.mysql.user.AdminUserMapper;
import cn.csg.iotplatform.module.system.enums.logger.LoginLogTypeEnum;
import cn.csg.iotplatform.module.system.enums.logger.LoginResultEnum;
import cn.csg.iotplatform.module.system.enums.oauth2.OAuth2ClientConstants;
import cn.csg.iotplatform.module.system.enums.security.SecurityStatusEnum;
import cn.csg.iotplatform.module.system.service.dept.DeptService;
import cn.csg.iotplatform.module.system.service.logger.LoginLogService;
import cn.csg.iotplatform.module.system.service.member.MemberService;
import cn.csg.iotplatform.module.system.service.oauth2.OAuth2TokenService;
import cn.csg.iotplatform.module.system.service.security.SecurityConfigService;
import cn.csg.iotplatform.module.system.service.user.AdminUserService;
import cn.csg.iotplatform.module.system.service.user.UserLoginManager;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.util.StringUtils;
import com.google.common.annotations.VisibleForTesting;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.Validator;
import java.time.LocalDateTime;
import java.util.Objects;

import static cn.csg.iotplatform.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.csg.iotplatform.framework.common.exception.util.ServiceExceptionUtil.exception0;
import static cn.csg.iotplatform.module.system.enums.ErrorCodeConstants.*;

/**
 * Auth Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Slf4j
public class AdminAuthServiceImpl implements AdminAuthService {

    @Resource
    private AdminUserService userService;
    @Resource
    private LoginLogService loginLogService;
    @Resource
    private OAuth2TokenService oauth2TokenService;
    @Resource
    private MemberService memberService;
    @Resource
    private Validator validator;
    @Resource
    private CaptchaService captchaService;
    @Resource
    private SecurityConfigService securityConfigService;
    @Resource
    private DeptService deptService;
    @Resource
    private UserLoginManager userLoginManager;

    @Resource
    private AdminUserMapper userMapper;

    /**
     * 验证码的开关，默认为 true
     */
    @Value("${iot.captcha.enable:true}")
    private Boolean captchaEnable;

    @Override
    public AdminUserDO authenticate(String username, String password) {

        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
        // 校验账号是否存在
        AdminUserDO user = userService.getUserByUsername(username);
        if (user == null) {
            createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
        }
        userLoginManager.loginLockUser(user.getId());
        if (!userService.isPasswordMatch(password, user.getPassword())) {
            createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            userLoginManager.loginFail(user.getId());
            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
        }
        // 校验是否禁用
        if (CommonStatusEnum.isDisable(user.getStatus())) {
            createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
            throw exception(AUTH_LOGIN_USER_DISABLED);
        }
        // 校验是否禁用(三级等保)
        if (CommonStatusEnumx.isDisable(user.getIsValid())) {
            createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
            throw exception(AUTH_LOGIN_USER_DISABLED);
        }
        return user;
    }

    @Override
    public AuthLoginRespVO login(AuthLoginReqVO reqVO) {


        // 校验验证码
        validateCaptcha(reqVO);

        if(reqVO.isEncrypt()){
            //校验加密账号和密码
            validateAccAndPassword(reqVO);
        }
        // 用户超过指定天数未登录系统自动禁用
        AdminUserDO userDO =  userMapper.selectByUsername(reqVO.getUsername());
        disableUser(userDO);

        // 使用账号密码，进行登录
        AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());

        //多点登录检查
        userLoginManager.checkLoginPlace(user.getId());

        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
    }

    private void disableUser(AdminUserDO userDO) {
        try{
            LocalDateTime loginDate = userDO.getLoginDate();

            String validDayStr = userMapper.getPasswordValidDay("disableUser");
            Integer validDay = 180;
            if(StringUtils.isNotBlank(validDayStr)) {
                validDay = Integer.parseInt(validDayStr);
            }
            long day = DateUtils.betweenNowDay(loginDate);
            if(day > validDay) {
                // 执行更新
                AdminUserDO updateObj = new AdminUserDO().setId(userDO.getId());
                updateObj.setIsValid(0);
                updateObj.setDisableReason("用户超过" + validDay + "天未登录系统自动禁用");
                userMapper.updateById(updateObj);
            }
        } catch (Exception e) {
            log.error("用户超过指定天数未登录系统自动禁用出错: {}", e);
        }
    }

    @Override
    public AuthLoginRespVO token(@Valid AuthTokenReqVO reqVO) {
        //校验加密账号和密码
        AuthLoginReqVO authLoginReqVO = AuthLoginReqVO.builder().clientId(reqVO.getClientId()).username(reqVO.getUsername())
            .password(reqVO.getPassword()).build();

        //校验加密账号和密码
        validateAccAndPassword(authLoginReqVO);

        // 使用账号密码，进行登录
        AdminUserDO user = authenticate(authLoginReqVO.getUsername(), authLoginReqVO.getPassword());

        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), authLoginReqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
    }

    private void validateAccAndPassword(AuthLoginReqVO reqVO) {
        try{
            SecurityConfigDO securityConfigDO = securityConfigService.getByClientId(reqVO.getClientId());
            if(securityConfigDO == null) throw exception(CLIENT_ID_NOT_EXISTS);
            if(SecurityStatusEnum.isDisable(securityConfigDO.getStatus())) throw exception(CLIENT_ID_NOT_DISABLE);
            reqVO.setUsername(RSAUtil.decrypt(reqVO.getUsername(),securityConfigDO.getPrivateKey()));
            reqVO.setPassword(RSAUtil.decrypt(reqVO.getPassword(),securityConfigDO.getPrivateKey()));
        }catch (Exception e){
            log.error("解密账号密码异常",e);
        }
    }

    private void createLoginLog(Long userId, String username,
                                LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
        // 插入登录日志
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setLogType(logTypeEnum.getType());
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setUserId(userId);
        reqDTO.setUserType(getUserType().getValue());
        reqDTO.setUsername(username);
        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserIp(ServletUtils.getClientIP());
        reqDTO.setResult(loginResult.getResult());
        AdminUserDO userDO = userService.getUser(userId);
        if(userDO != null){
            reqDTO.setRegisterTime(userDO.getCreateTime());
            reqDTO.setNickname(userDO.getNickname());
            reqDTO.setDeptId(userDO.getDeptId());
            DeptDO deptDO = deptService.getDept(userDO.getDeptId());
            if(deptDO != null){
                reqDTO.setDeptName(deptDO.getName());
            }
        }
        loginLogService.createLoginLog(reqDTO);
        // 更新最后登录时间
        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
            userService.updateUserLogin(userId, ServletUtils.getClientIP());
        }
    }

    @VisibleForTesting
    void validateCaptcha(AuthLoginReqVO reqVO) {
        // 如果验证码关闭，则不进行校验
        if (!captchaEnable) {
            return;
        }
        // 校验验证码
        ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());
        ResponseModel response = captchaService.verification(captchaVO);
        // 验证不通过
        if (!response.isSuccess()) {
            // 创建登录失败日志（验证码不正确)
            createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);
            throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());
        }
    }

    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
        // 插入登陆日志
        createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
        // 创建访问令牌
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
                OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
        // 构建返回结果
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    @Override
    public AuthLoginRespVO refreshToken(String refreshToken) {
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    @Override
    public Integer checkPassword(AuthLoginReqVO reqVO) {
        validateAccAndPassword(reqVO);
        AdminUserDO user = userService.getUser(WebFrameworkUtils.getLoginUserId());
        if (!userService.isPasswordMatch(reqVO.getPassword(), user.getPassword())) {
            return 0;
        }
        return 1;
    }

    @Override
    public CheckTokenRespVO checkToken(CheckTokenReqVO reqVO) {
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(reqVO.getToken());
        AdminUserDO adminUserDO = userMapper.getUserData(accessTokenDO.getUserId());
        DeptDO deptDO = deptService.getDept(adminUserDO.getDeptId());
        return buildCheckTokenRespVO(accessTokenDO, adminUserDO, deptDO);
    }

    @Override
    public AuthLoginRespVO createToken(AdminUserDO userDO,LoginLogTypeEnum logType) {
        if(userDO == null) throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "用户不存在");
        return createTokenAfterLoginSuccess(userDO.getId(), userDO.getUsername(), logType);
    }
    @Override
    public AuthLoginRespVO getToken(AdminUserDO userDO) {
        if(userDO == null) throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "用户不存在");

        // 创建访问令牌
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userDO.getId(), getUserType().getValue(),
                OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
        // 构建返回结果
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }


    private CheckTokenRespVO buildCheckTokenRespVO(OAuth2AccessTokenDO accessTokenDO, AdminUserDO adminUserDO, DeptDO deptDO) {
        if(adminUserDO == null) throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "用户不存在");
        if(deptDO == null) throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "用户部门不存在");
        CheckTokenRespVO respVO = CheckTokenRespVO.builder().build();
        respVO.setUserId(accessTokenDO.getUserId());
        respVO.setUsername(adminUserDO.getUsername());
        respVO.setNickname(adminUserDO.getNickname());
        respVO.setSex(adminUserDO.getSex());
        respVO.setDeptId(adminUserDO.getDeptId());
        respVO.setDeptName(deptDO.getName());
        respVO.setOrgCode(deptDO.getOrgCode());
        return respVO;
    }

    @Override
    public void logout(String token, Integer logType) {
        // 删除访问令牌
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
        if (accessTokenDO == null) {
            return;
        }
        // 删除成功，则记录登出日志
        createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
    }

    private void createLogoutLog(Long userId, Integer userType, Integer logType) {
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setLogType(logType);
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setUserId(userId);
        reqDTO.setUserType(userType);
        if (ObjectUtil.equal(getUserType().getValue(), userType)) {
            reqDTO.setUsername(getUsername(userId));
        } else {
            reqDTO.setUsername(memberService.getMemberUserMobile(userId));
        }
        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserIp(ServletUtils.getClientIP());
        reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
        loginLogService.createLoginLog(reqDTO);
    }

    private String getUsername(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserDO user = userService.getUser(userId);
        return user != null ? user.getUsername() : null;
    }

    private UserTypeEnum getUserType() {
        return UserTypeEnum.ADMIN;
    }

}
